"""
Abstract interfaces to regress one node on its parents
"""
from abc import ABC
import logging

import numpy as np

from ..common import zscore


class Regressor(ABC):
    """
    回归器基类
    
    用于建立节点与其父节点之间的条件概率关系：x ~ P(x | pa(X))
    其中：
    - x 是当前节点的值
    - pa(X) 是节点的父节点集合
    - P(x | pa(X)) 表示在给定父节点值的条件下，当前节点的概率分布
    """

    def __init__(self):
        """
        初始化回归器
        设置日志记录器，用于记录回归过程中的警告和错误
        """
        klass = self.__class__
        self._logger = logging.getLogger(f"{klass.__module__}.{klass.__name__}")

    @staticmethod
    def _zscore(train_y: np.ndarray, test_y: np.ndarray) -> np.ndarray:
        """
        计算Z分数
        
        Args:
            train_y: 训练集目标值，用于建立正态分布基线
            test_y: 测试集目标值，需要计算Z分数的数据
            
        Returns:
            np.ndarray: 标准化后的Z分数
        """
        return zscore(train_y=train_y, test_y=test_y)

    def _score(
        self,
        train_x: np.ndarray,    # 训练集特征矩阵
        test_x: np.ndarray,     # 测试集特征矩阵
        train_y: np.ndarray,    # 训练集目标值
        test_y: np.ndarray,     # 测试集目标值
    ) -> np.ndarray:
        """
        计算异常分数的具体实现方法
        
        这是一个抽象方法，需要由子类实现具体的评分逻辑
        """
        raise NotImplementedError

    def score(
        self,
        train_x: np.ndarray,    # 训练集特征矩阵
        test_x: np.ndarray,     # 测试集特征矩阵
        train_y: np.ndarray,    # 训练集目标值
        test_y: np.ndarray,     # 测试集目标值
    ) -> np.ndarray:
        """
        评估测试集中的值在多大程度上违反了回归关系
        
        处理流程：
        1. 如果没有特征数据，直接计算目标值的Z分数
        2. 尝试使用具体的评分方法计算异常分数
        3. 如果评分过程出错，回退到简单的Z分数计算
        
        异常处理：
        - 捕获ValueError异常并记录警告日志
        - 在异常情况下使用Z分数作为备选方案
        """
        # 如果没有特征数据，直接计算Z分数
        if len(train_x) == 0:
            return self._zscore(train_y=train_y, test_y=test_y)
        try:
            # 调用具体的评分实现
            return self._score(
                train_x=train_x, test_x=test_x, train_y=train_y, test_y=test_y
            )
        except ValueError as err:
            return self._zscore(train_y=train_y, test_y=test_y)
